package org.dromara.tearoom.mqtt;

import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;

/**
 * 启动服务的时候开启监听客户端
 * @author CKJ
 * @description: 启动服务的时候开启监听客户端
 * @date 2025/4/15 17:17
 */
@Slf4j
@Configuration
public class MqttConfig {

    @Resource
    private MqttProperties mqttProperties;
    /**
     * 客户端对象
     */
    private MqttClient client;
    /**
     * 客户端连接服务端
     * 目前只支持一个 MQTT服务端
     */
    public boolean connect() {
        if (isMqtt()) {
            return false;
        }
        try {
            // 检查客户端是否已经连接
            if (client != null && client.isConnected()) {
                log.info("MQTT客户端已连接");
                return true;
            }
            //new MemoryPersistence() 使用内存持久化
            // 优点：不会在文件系统中创建任何文件（如 .lck 文件），适合对会话持久性没有要求的场景。
            // 缺点:  客户端断开连接或重启后，会话数据会丢失，无法保留订阅信息和未发送的消息

            // String persistenceDirectory = "/path/to/your/mqtt/persistence";
            //new MqttDefaultFilePersistence(persistenceDirectory) 使用文件持久化
            //如果persistenceDirectory 不写 他默认创建 根目录 linux要给权限
            // 优点: 客户端断开连接或重启后，能够保留订阅信息和未发送的消息。这对于需要保持会话状态的应用非常重要
            // 缺点 会在指定的目录中创建文件（如 .lck 文件），需要确保指定的目录是有效的，并且应用有权限访问该目录

            //创建MQTT客户端对象
            client = new MqttClient("tcp://"+mqttProperties.getUrl() + ":" + mqttProperties.getPort(), mqttProperties.getClientId(), new MemoryPersistence());
            //连接设置
            MqttConnectOptions options = new MqttConnectOptions();
            //是否清空session，设置false表示服务器会保留客户端的连接记录（订阅主题，qos）,客户端重连之后能获取到服务器在客户端断开连接期间推送的消息
            //设置为true表示每次连接服务器都是以新的身份
            options.setCleanSession(false);
            //设置连接用户名
            options.setUserName(mqttProperties.getUsername());
            //设置连接密码
            options.setPassword(mqttProperties.getPassword().toCharArray());


            options.setAutomaticReconnect(true);  // 启用自动重连
            //设置超时时间，单位为秒  如果在指定的时间内未能建立连接，客户端会放弃连接尝试并抛出异常。
            options.setConnectionTimeout(mqttProperties.getTimeout());
            //设置心跳时间 单位为秒，表示服务器每隔 1.5*20秒的时间向客户端发送心跳判断客户端是否在线
            options.setKeepAliveInterval(mqttProperties.getKeepAlive());
            //设置遗嘱消息的话题，若客户端和服务器之间的连接意外断开，服务器将发布客户端的遗嘱信息
            //  options.setWill("willTopic",(mqttClientId + ":与服务器断开连接").getBytes(),0,false);
            //配置公共的回调地址用于处理全局异常
            client.setCallback(new PublicCallback());
            client.connect(options);
            return true;
        } catch (MqttException e) {
            log.error("MQTT启动报错：{}", e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * qos
     * 0  最多一次传递【适用于对消息丢失不敏感的场景，如传感器数据频繁发送，可以接受偶尔的数据丢失】
     * 1 至少一次传递  【消息至少传递一次，但可能会重复（即重复消息）】
     * 2 仅一次传递 【消息确保仅传递一次，既不会丢失也不会重复。】
     * retained
     * 保留消息：如果 retained 参数设置为 true，消息会被代理保留。代理将记住这个消息，并在新客户端订阅该主题时立即发送这个消息。
     * 非保留消息：如果 retained 参数设置为 false，消息不会被保留，只会发送给当前在线并订阅该主题的客户端。
     * topic    主题
     * message  内容
     */
    public boolean publish(int qos, boolean retained, String topic, String message) {
        if (isMqtt()) {
            return false;
        }
        log.info("topic为:【" + topic + "】,qos为：【" + qos + "】 mqtt 发布数据为：" + message);

        MqttMessage mqttMessage = new MqttMessage();
        mqttMessage.setQos(qos);
        //代理将记住这个消息，并在新客户端订阅该主题时立即发送这个消息。
        mqttMessage.setRetained(retained);
        mqttMessage.setPayload(message.getBytes());
        //主题的目的地，用于发布信息
        MqttTopic mqttTopic = client.getTopic(topic);

        MqttDeliveryToken token;
        try {
            //将指定消息发布到主题，但不等待消息传递完成，返回的token可用于跟踪消息的传递状态
            token = mqttTopic.publish(mqttMessage);
            //token.waitForCompletion(); // 等待完成 会堵塞
            return true;
        } catch (MqttException e) {
            log.warn("ClientId【" + mqttProperties.getClientId() + "】发布失败！主题【" + topic + "】，发布数据为：" + message);
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 断开连接
     */
    public void disConnect() {
        try {
            if(client!=null && client.isConnected()){
                client.disconnect();
            }
        } catch (MqttException e) {
            log.error("MQTT断开连接失败: {}", e.getMessage());
            e.printStackTrace();
        }
    }

    /***
     *  手动连接
     *  可用于断线后 手动重连
     * ***/
    public boolean againConnect() {
        try {
            if (client != null && !client.isConnected()) {
                connect();
            }
            return true;
        } catch (Exception e) {
            log.error("MQTT重连失败: {}", e.getMessage());
            return false;
        }
    }

    //验证是否启动mqtt连接
    private boolean isMqtt() {
        if (StringUtils.isEmpty(mqttProperties.getUrl()) || ObjectUtil.isEmpty(mqttProperties.getPort())
                || StringUtils.isEmpty(mqttProperties.getUsername()) || StringUtils.isEmpty(mqttProperties.getPassword())
                || StringUtils.isEmpty(mqttProperties.getClientId())
        ){
            log.info("==========mqtt 参数不全，无需启动MQTT连接==================");
            return true;
        }
        if (!mqttProperties.getIsOpen()){
            log.info("==========mqtt 设置关闭，无需启动MQTT连接==================");
            return true;
        }
        return false;
    }


    /**
     * 订阅指定主题
     *
     * @param topic 订阅的主题
     * @param qos   订阅的服务质量
     */
    public boolean subscribe(String topic, int qos,IMqttMessageListener listener) {
        if (isMqtt()) {
            return false;
        }
        try {
            if (client != null && client.isConnected()) {
                client.subscribe(topic, qos,listener);

                log.info("订阅主题 {} 成功！", topic);
            } else {
                log.error("MQTT客户端尚未连接，无法订阅主题 {}！", topic);
            }
            return true;
        } catch (MqttException e) {
            log.error("订阅主题 {} 失败：{}", topic, e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 批量订阅主题
     * 消息等级，和主题数组一一对应，服务端将按照指定等级给订阅了主题的客户端推送消息
     *
     * @param topic 订阅的主题集合
     * @param qos   订阅的服务质量集合
     */
    public boolean subscribe(String[] topic, int[] qos) {
        if (isMqtt()) {
            return false;
        }
        try {
            if (client != null && client.isConnected()) {
                client.subscribe(topic, qos);
                log.info("订阅主题 {} 成功！", topic);
            } else {
                log.error("MQTT客户端尚未连接，无法订阅主题 {}！", topic);
            }
            return true;
        } catch (MqttException e) {
            log.error("订阅主题 {} 失败：{}", topic, e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    @PreDestroy
    public void cleanUp() {
        log.info("关闭MQTT连接！");
        disConnect();
    }


}
